#!/usr/bin/env python
# -*- coding: utf-8 -*-
#=======================================#
# @author: RedSoxFan and NerfSoldier
# @copyright: 2012 RedSoxFan and NerfSoldier
# @license: GNU General Public License v3.0 
#           <http://www.gnu.org/copyleft/gpl.html>
# @summary: Handles Workspaces
#=======================================#

#=======================================#
# Imports
#=======================================#
# Python
from PyQt4 import QtGui, QtCore
from editor import Editor
import os
import socket
import subprocess
import sys
import threading
import time
# DarkDev
from project import Project
from projects import ProjectTree
#=======================================#

class WorkspaceWindow(QtGui.QMainWindow):
    def __init__(self,workspace,darkdev):
        super(WorkspaceWindow,self).__init__()
        
        # Initialize Workspace
        self.workspace = workspace # Store Reference To Workspace
        self.darkdev = darkdev # Store Reference To DarkDev
        
        # Initialize GUI
        self.setWindowState(QtCore.Qt.WindowMaximized)
        self.setWindowTitle("Workspace %s"%self.workspace.name)
        self.setToolTip(self.workspace.location)
        self.setWindowIcon(QtGui.QIcon('resources/workspace/workspace.png'))
        self.setMinimumSize(450, 300)
        self.subwindow = self.darkdev.mdi.addSubWindow(self) # Add Window To MDI Area
        self.subwindow.setToolTip(self.workspace.location)
        self.load_ddcg()
        
        # DarkDev > MenuBar > Workspace > Workspace Name
        self.menuitem = QtGui.QMenu('Workspace %s'%self.workspace.name, self)
        self.darkdev.menu_ws.addMenu(self.menuitem)
        # DarkDev > MenuBar > Workspace > Workspace Name > Focus
        self.menuitem_focus = QtGui.QAction('Focus', self.menuitem)
        self.menuitem_focus.triggered.connect(self.focusNextChild)
        self.menuitem.addAction(self.menuitem_focus)
        
        self.show()
        
        # Initialize Central Widget
        self.tabs = QtGui.QTabWidget()
        self.tabs.setFocusPolicy(QtCore.Qt.NoFocus)
        self.tabs.setTabsClosable(True)
        self.tabs.setMovable(True)
        self.tabs.tabCloseRequested.connect(lambda i:self.tabs.removeTab(i))
        self.setCentralWidget(self.tabs)
        
        # Initialize Docks
        self.menuitem.addSeparator()
        
        # Docks > Projects
        self.proj_dock = QtGui.QDockWidget()
        self.proj_dock.setObjectName("Projects")
        self.proj_dock.setWindowTitle("Projects")
        self.proj_items = {}
        self.proj = ProjectTree(self.workspace)
        self.proj.setHeaderHidden(True)
        self.proj.setMinimumWidth(120)
        self.proj.setIndentation(15)
        self.proj.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.proj.itemDoubleClicked.connect(self.item_dbclicked)
        self.proj.customContextMenuRequested.connect(self.item_rclicked)
        self.proj_dock.setWidget(self.proj)
        self.addDockWidget(QtCore.Qt.LeftDockWidgetArea,self.proj_dock)
        self.menuitem.addAction(self.proj_dock.toggleViewAction())
        
        # Docks > Console
        self.con_dock = QtGui.QDockWidget()
        self.con_dock.setObjectName("Console")
        self.con_dock.setWindowTitle("Console")
        con = QtGui.QTextEdit()
        con.setReadOnly(True)
        con.setAutoFillBackground(True)
        con.setStyleSheet("background:#111;color:#CCC")
        self.con_dock.setWidget(con)
        self.addDockWidget(QtCore.Qt.BottomDockWidgetArea,self.con_dock)
        self.menuitem.addAction(self.con_dock.toggleViewAction())
        
    def item_dbclicked(self,item,column):
        try:
            self.proj_items[item]['db']()
        except:
            pass
    
    def item_rclicked(self,point):
        try:
            item = self.proj.itemAt(point)
            self.proj_items[item]['r'](point)
        except:
            self.workspace_menu(point)
    
    def workspace_menu(self,point):
        '''
        Show Context Menu For Workspace At QPoint point
        '''
        menu = QtGui.QMenu(self.workspace.name)
        # New File
        new_proj = QtGui.QAction(QtGui.QIcon('resources/project/new.png'),'New Project',menu)
        new_proj.setStatusTip('New Project')
        new_proj.triggered.connect(lambda *a,**k:Project.create(self.workspace,self.darkdev))
        menu.addAction(new_proj)
        # Show
        menu.exec_(self.proj.mapToGlobal(point))
            
    def load_ddcg(self):
        #self.ddcg = DDCGObject(self)
        #for name in [os.path.join(os.path.expanduser("~"),".DarkDev","config.ddcg"),"config.ddcg","resources/default.ddcg"]:
        #    if os.path.exists(name) and self.ddcg.load(name):
        #        self.logger.debug("Loaded DDCG From %r"%(os.path.abspath(name)))
        #        break
        self.ddcg = self.darkdev.ddcg
    
    def open_file(self,item,project,index=-1,parser=None):
        fil = str(item.toolTip(0))
        if parser:
            e=Editor(project,self.darkdev.get_parser(parser),fil)
            if index==-1:
                i=self.tabs.addTab(e,os.path.basename(fil))
            else:
                i=self.tabs.insertTab(index,e,os.path.basename(fil))
            self.tabs.setTabToolTip(i,fil)
            self.tabs.setTabWhatsThis(i,parser)
            if index==-1: self.tabs.setCurrentIndex(i)
        elif self.darkdev.can_open(fil):
            for i in range(self.tabs.count()):
                if self.tabs.tabToolTip(i)==fil:
                    self.tabs.setCurrentIndex(i)
                    return
            parsername,parser=self.darkdev.get_parser_for_file_with_name(fil)
            e=Editor(project,parser,fil)
            if index==-1:
                i=self.tabs.addTab(e,os.path.basename(fil))
            else:
                i=self.tabs.insertTab(index,e,os.path.basename(fil))
            self.tabs.setTabToolTip(i,fil)
            self.tabs.setTabWhatsThis(i,parsername)
            if index==-1: self.tabs.setCurrentIndex(i)
        else:
            self.open_outside(item)
            
    def open_outside(self,item):
        fil = str(item.toolTip(0))
        if sys.platform.startswith('darwin'):
            subprocess.call(('open', fil))
        elif os.name == 'nt':
            os.startfile(fil)
        elif os.name == 'posix':
            fnull = open(os.devnull, 'w')
            subprocess.call(('xdg-open', fil), stdout = fnull, stderr = fnull)
            fnull.close()
    
    def closeEvent(self, *args, **kwargs):
        self.workspace.clean()
        
class Workspace(object):
    def __init__(self, darkdev, fname):
        self.darkdev = darkdev
        self.location = str(fname) # Location Of Workspace
        self.name = str(fname) # For Future Use
        self.hostname = socket.gethostname()
        self.s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.s.bind(("127.0.0.1",0))
        self.port = self.s.getsockname()[1]
        self.passcode = str(time.time())+self.hostname+str(self.port)+self.location # Passcode. Implementation May Change
        with open(os.path.join(fname,".lock"),"w") as f: # Update .lock file
            f.write("%s\n"%self.hostname)
            f.write("%s\n"%self.port)
            f.write("%s"%self.passcode)
            f.flush()
        self.s.listen(1)
        # Create Daemon Thread To Listen On Port
        t = threading.Thread(target=self.__listen)
        t.daemon=True
        t.start()
        # Open Window
        self.window = WorkspaceWindow(self, self.darkdev)
    @staticmethod
    def new_workspace(darkdev):
        # Show Directory Dialog Selection
        fd = QtGui.QFileDialog()
        fd.setFileMode(QtGui.QFileDialog.Directory)
        fd.setOption(QtGui.QFileDialog.ShowDirsOnly,True)
        fname = fd.getExistingDirectory(darkdev,'Select Workspace Location',os.path.expanduser("~"))
        if fname:
            entry = QtGui.QInputDialog.getText(darkdev, "Workspace Name", "What would you like to name the workspace?",text=str(fname))
            w=Workspace.open_workspace(darkdev,str(fname))
            if entry[1]: 
                w.name=str(entry[0])
                w.window.menuitem.setTitle(w.name)
                w.window.setWindowTitle(w.name)
            return w
        return None
    @staticmethod
    def open_workspace(darkdev,fname):
        fname = str(fname)
        # Test To See If Workspace Is Open
        if os.path.exists(os.path.join(fname,".lock")): # Check To See If Lock File Exists
            with open(os.path.join(fname,".lock"),"r") as lf:
                if lf.readline().strip()==socket.gethostname(): # Check To See If Hostnames Match
                    try:
                        # Try To Connect To Socket
                        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        s.connect(('127.0.0.1', int(lf.readline())))
                        s.settimeout(2) # 2 Second Timeout
                        c = 'DDLM%sLMDD'%lf.readline() # DarkDev Locking Mechanism Encapsulation
                        s.sendall(c)
                        data = s.recv(len(c))
                        if data!=c: raise Exception, "No/Incorrect Reply" # Check To See If Pass
                        x = QtGui.QMessageBox().information(darkdev, "Workspace Override Required", "The workspace appears to be OPEN.\nIt is NOT recommended to open a workspace multiple times.\nWould you like to approve the override of the locking mechanism and open the workspace anyway?", QtGui.QMessageBox.Yes|QtGui.QMessageBox.No,QtGui.QMessageBox.No)
                        if x==QtGui.QMessageBox().No: return
                    except:
                        pass
                    else:
                        s.close()
                        s = None
        # Workspace does not appear open (or has user override) and is ready for opening
        w=Workspace(darkdev,os.path.abspath(fname))
        for folder in [x for x in os.listdir(w.location) if os.path.isdir(os.path.join(w.location,x)) and x[0]!="."]:
            Project.load(w, w.darkdev, folder)
        # Load Config
        w.load_config()
        return w
    def __listen(self):
        while self.s:
            # Listen On Port
            sock = self.s.accept()[0]
            data = sock.recv(len(self.passcode)+8) # Capture len(self.passcode) Bytes + 8 Encapsulation Bytes
            if data=="DDLM%sLMDD"%self.passcode: # Verify Passcode and Encapsulation
                sock.sendall(data) # Echo Passcode
    def clean(self):
        self.darkdev.logger.info("Cleaning Workspace %r"%self.name)
        self.s = None        
        try:
            os.unlink(os.path.join(self.location,".lock"))
        except Exception, err:
            pass
        self.save_config()
        self.darkdev.ws.remove(self)
        
    def save_config(self):
        self.darkdev.logger.info("Saving Config For Workspace %r"%self.name)
        with open(os.path.join(self.location,".ddws"),"w") as f:
            f.write("name=%s\r\n"%self.name)
            f.write("state=%s\r\n"%self.window.saveState().data())
            f.write("maximized=%s\r\n"%self.window.subwindow.isMaximized())
            f.write("minimized=%s\r\n"%self.window.subwindow.isMinimized())
            f.write("bounds=(%s,%s,%s,%s)\r\n"%(self.window.subwindow.x(),self.window.subwindow.y(),self.window.subwindow.width(),self.window.subwindow.height()))
            f.write("tabCurrent=%r\r\n"%self.window.tabs.tabBar().currentIndex())
            t=self.window.tabs
            for x in xrange(self.window.tabs.tabBar().count()):
                # tab#=(filename,parser)
                f.write("tab%s=(%s,%s)\r\n"%(x,str(t.tabToolTip(x)).replace(self.location,""),t.tabWhatsThis(x)))
                
    def load_config(self):
        self.darkdev.logger.info("Loading Config For Workspace At %r"%self.location)
        if os.path.exists(os.path.join(self.location,".ddws")):
            with open(os.path.join(self.location,".ddws"),"r") as f:
                ind = 0
                for l in f.read().split("\r\n"):
                    if "=" not in l: continue
                    k,v=l.split("=",2)
                    if k=="name": 
                        self.name=v
                        self.window.menuitem.setTitle(self.name)
                        self.window.setWindowTitle(self.name)
                    elif k=="maximized": 
                        if eval(v): self.window.subwindow.setWindowState(QtCore.Qt.WindowMaximized)
                        else: self.window.subwindow.setWindowState(QtCore.Qt.WindowNoState)
                    elif k=="minimized": 
                        if eval(v): self.window.subwindow.setWindowState(QtCore.Qt.WindowMinimized)
                    elif k=="bounds":
                        self.window.subwindow.setGeometry(*eval(v))
                    elif k=="state": self.window.restoreState(QtCore.QByteArray.fromRawData(v))
                    elif k=="tabCurrent": ind = int(v)
                    elif k.startswith("tab"):
                        i=int(k.replace("tab",""))
                        fp,pn=v[1:-1].split(",",2)
                        if fp[0] in ["\\","/"]: fp=fp[1:]
                        fp=os.path.join(self.location,fp.replace("\\",os.sep).replace("/",os.sep))
                        item,proj = self.window.proj.project_for_file(fp)
                        if item and proj:
                            self.window.open_file(item, proj, i, pn)
                        del i,fp,pn,item,proj
                    del k,v
                self.window.tabs.setCurrentIndex(ind)
                del ind